/*************************************************************************
 *                                                                       *
 * This file is part of the FreeMAPs distribution.  For full copyright   *
 * information, see COPYRIGHT and COPYING.LESSER.                        *
 *                                                                       *
 * Author:           sqbang                                              *
 * Email:            sqbang@ustc.edu                                     *
 * Date:             2011.12.22                                          *
 *                                                                       *
 *************************************************************************/

//declaration header
#include "COMMON/equa/PDEEquationEnsembleStrategy.h"

//COMMON headers
#include "COMMON/tbox/InputDatabase.h"
#include "COMMON/tbox/LogMessage.h"
#include "COMMON/equa/HeatConductionEquationEnsemble.h"
#include "COMMON/equa/ElasticMechanicsEquationEnsemble.h"
#include "COMMON/equa/EquationDomain.h"
#include "COMMON/syst/SystemStrategy.h"
#include "COMMON/tbox/StringToEnum.h"
#include "COMMON/bdry/IrregularBoundaryConditions.h"
#include "COMMON/bdry/InitializationCondition.h"


namespace FreeMAPs
{

PDEEquationEnsembleStrategy::PDEEquationEnsembleStrategy (
    const std::string& parent_name,
    const std::string& equation_ensemble_name/*,
    const int sys_id*/):
    Parent (parent_name, equation_ensemble_name),
    d_system(0),
    d_equation_domain(0),
    d_irregular_bdry_conds(0),
    d_do_write_character_quantity (true)
{
    //when write file, need these two objects
    d_system = new SystemStrategy(this->get_full_name(), "system", *this);
    d_equation_domain = new EquationDomain(this);
    d_irregular_bdry_conds = new IrregularBoundaryConditions (this->get_full_name(), "irregular_boundary_conditions");
}



PDEEquationEnsembleStrategy::~PDEEquationEnsembleStrategy()
{
    this->clear();
}



void
PDEEquationEnsembleStrategy::clear()
{
    Utilities::clear_pointer_object (d_equation_domain);
    Utilities::clear_pointer_object (d_system);
    Utilities::clear_pointer_object (d_irregular_bdry_conds);
    Utilities::clear_pointer_object (d_init_conditions);
}



bool
PDEEquationEnsembleStrategy::use_structured_mesh() const
{
    //return d_equation_domain->get_region()->use_structured_mesh();
    return false;//unstructured grid
}



PDEEquationEnsembleStrategy*
PDEEquationEnsembleStrategy::create_object (
    const EquationEnsembleType eq_type,
    const std::string& parent_name,
    const std::string& object_name/*,
    const int sys_id*/)
{
    PDEEquationEnsembleStrategy* equation (NULL);
    switch (eq_type)
    {
    case EquationEnsembleType::HEAT_CONDUCTION:
        {
            equation = new HeatConductionEquationEnsemble (parent_name, object_name/*, sys_id*/);

            break;
        }
#if 0
    case EquationEnsembleType::NAVIER_STOKES:
        {
            //equation = new FluidEquationEnsemble (parent_name, object_name, sys_id);
            FREEMAPS_NOT_FINISHED();
            break;
        }
    case EquationEnsembleType::CREEPING_FLOW:
        {
            equation = new CreepingFlowEquationEnsemble (parent_name, object_name, sys_id);

            break;
        }
#endif
    case EquationEnsembleType::ELASTIC_MECHANICS:
        {
            equation = new ElasticMechanicsEquationEnsemble (parent_name, object_name/*, sys_id*/);

            break;
        }
    default:
        {
            FREEMAPS_FIND_BUG();
        }
    }

    return equation;
}



void
PDEEquationEnsembleStrategy::add_initialize_condition(
        const std::string ic_name,
        const double init_value)
{
    InitializationCondition* ic = new InitializationCondition (
                this->get_full_name() + "-->initialize_conditions",
                ic_name);
    ic->set_init_value(init_value);

    d_init_conditions.insert(std::make_pair(ic_name, ic));
}



void
PDEEquationEnsembleStrategy::set_domain_region_name(
        const std::string domain_region_name)
{
    d_equation_domain->set_region_name(domain_region_name);
}



const std::string&
PDEEquationEnsembleStrategy::get_domain_region_name() const
{
    return d_equation_domain->get_region_name();
}



SystemStrategy*
PDEEquationEnsembleStrategy::get_system_strategy()
{
    return d_system;
}



bool
PDEEquationEnsembleStrategy::check_all_info_db (
        const std::string equation_ensemble_name,
        const InputDatabase* db)
{
    //type is checked, before
    const EquationEnsembleType eq_type = db->get_enum<EquationEnsembleType>("type");


    //do_write_character_quantity
    if( !db->key_exists("do_write_character_quantity") )
    {
        FREEMAPS_ERROR("in \"simulation_ensemble\", in \"" + equation_ensemble_name + "\", can not find  \"do_write_character_quantity\" \n");
        return false;
    }
    if( !db->is_bool("do_write_character_quantity") )
    {
        FREEMAPS_ERROR("in \"simulation_ensemble\",  in \"" + equation_ensemble_name + "\", \"do_write_character_quantity\" should be bool \n");
        return false;
    }

    if( eq_type == EquationEnsembleType::HEAT_CONDUCTION )// if type is HEAT_CONDUCTION
    {
        //initialize_conditions
        if( !db->key_exists("initialize_conditions") )
        {
            FREEMAPS_ERROR("in \"simulation_ensemble\", in \"" + equation_ensemble_name + "\", can not find  \"initialize_conditions\" \n");
            return false;
        }
        if( !db->is_database("initialize_conditions") )
        {
            FREEMAPS_ERROR("in \"simulation_ensemble\",  in \"" + equation_ensemble_name + "\", \"initialize_conditions\" should be database \n");
            return false;
        }
    }

    //irregular_boundary_conditions
    if( !db->key_exists("irregular_boundary_conditions") )
    {
        FREEMAPS_ERROR("in \"simulation_ensemble\", in \"" + equation_ensemble_name + "\", can not find  \"irregular_boundary_conditions\" \n");
        return false;
    }
    if( !db->is_database("irregular_boundary_conditions") )
    {
        FREEMAPS_ERROR("in \"simulation_ensemble\",  in \"" + equation_ensemble_name + "\", \"irregular_boundary_conditions\" should be database \n");
        return false;
    }

    return true;
}



void
PDEEquationEnsembleStrategy::get_from_input (
        const InputDatabase* db)
{
    this->active_verbose_mode (0);

    this->prepare_for_use (db);

    db->insert_allowable_key ("type");
    db->insert_allowable_key ("do_write_character_quantity");
    db->insert_allowable_key ("mesh_name");
#ifdef FREEMAPS_HAVE_SAMRAI
    db->insert_allowable_key ("tag_and_initialize");
#endif
    db->insert_allowable_key ("equations");
    db->insert_allowable_key ("discrete_parameters");
    db->insert_allowable_key ("domain");
    db->insert_allowable_key ("time_info");
    db->insert_allowable_key ("system");
    db->insert_allowable_key ("initialize_conditions");
#ifdef FREEMAPS_HAVE_SAMRAI
    db->insert_allowable_key ("regular_boundary_conditions");
#endif
    db->insert_allowable_key ("irregular_boundary_conditions");

    db->insert_allowable_key ("refine_patch_names");

    db->check_allowable_keys ();

    /*
     * build do_write_character_quantity
     */
    {
        if (db->key_exists ("do_write_character_quantity") && db->is_bool("do_write_character_quantity"))
        {
            db->check_optional_key ("do_write_character_quantity", DataType::BOOL, true, false);
            d_do_write_character_quantity = db->get_bool_with_default ("do_write_character_quantity", d_do_write_character_quantity);
        }
    }

    /*
     * build domain, it should be build before system is build.
     * because system's dof map should know it is a structured mesh or unstructured mesh.
     */
    {
        if(db->key_exists("domain") && db->is_database("domain"))
        {
            db->check_requisite_key ("domain", DataType::DATABASE, false, false);
            d_equation_domain->get_from_input (db->get_database ("domain"));
        }
    }

    /*
     * build system
     */
    {
        if(db->key_exists("system") && db->is_database("system"))
        {
            db->check_requisite_key ("system", DataType::DATABASE, false, false);
            d_system->get_from_input (db->get_database ("system"));
        }

    }

    /*
     * initialization conditions
     */
    {
        if(db->key_exists("initialize_conditions") && db->is_database("initialize_conditions"))
        {
            db->check_optional_key ("initialize_conditions", DataType::DATABASE, false, false);
            if (db->key_exists ("initialize_conditions"))
            {
                const InputDatabase* init_db = db->get_database ("initialize_conditions");

                if(init_db->key_exists("ic_names") && init_db->is_string("ic_names"))
                {
                    init_db->insert_allowable_key_with_items ("ic_names");
                    init_db->check_allowable_keys();

                    init_db->check_requisite_key ("ic_names", DataType::STRING, false, true);
                    init_db->check_duplicated_string_value ("ic_names");

                    const std::vector<std::string>& ic_names = init_db->get_string_vector ("ic_names");
                    for (std::size_t i = 0; i < ic_names.size(); ++i)
                    {
                        const std::string& ic_name = ic_names[i];

                        if(init_db->key_exists(ic_name) && init_db->is_database(ic_name))
                        {
                            InitializationCondition* ic = new InitializationCondition (
                                        this->get_full_name() + "-->initialize_conditions",
                                        ic_name);

                            init_db->check_requisite_database (ic_name);
                            ic->get_from_input (init_db->get_database (ic_name), *this);

                            d_init_conditions.insert (std::make_pair (ic_name, ic));
                        }
                    }
                }
            }
        }
    }

    /*
     * build irregular boundary conditions
     */
    {
        if(db->key_exists("irregular_boundary_conditions") && db->is_database("irregular_boundary_conditions"))
        {
            db->check_requisite_database ("irregular_boundary_conditions");
            d_irregular_bdry_conds->get_from_input (db->get_database ("irregular_boundary_conditions"), this);
        }
    }
}



std::ostream&
operator << (
    std::ostream& os,
    const PDEEquationEnsembleStrategy& rhs)
{
    std::ostringstream oss;

    os << rhs.get_equation_ensemble_name() << "\n{\n";

    /*
     * output type
     */
    {
        os << LogMessage::get_prefix (ONE_TAB) << "type = \"";
        os << Utilities::enum_to_string (rhs.get_equation_ensemble_type()) << "\"\n";
    }
    /*
     * output do_write_character_quantity
     */
    {
        os << LogMessage::get_prefix (ONE_TAB) << "do_write_character_quantity = ";
        os << LogMessage::get_bool_string (rhs.do_write_character_quantity()) << "\n\n";
    }
    /*
     * output equations
     */
    {
        os << LogMessage::get_log_info (rhs.get_info_from_derived_object(), ONE_TAB) << "\n\n";
    }
    /*
     * output init_conds
     */
    {
//        const EquationEnsembleType type = rhs.get_equation_ensemble_type();
//        if(type == EquationEnsembleType::HEAT_CONDUCTION)
//        {
            if (!rhs.d_init_conditions.empty())
            {
                os << LogMessage::get_prefix (ONE_TAB) << "initialize_conditions\n";
                os << LogMessage::get_prefix (ONE_TAB) << "{\n";

                os << LogMessage::get_log_info_from_map_keys ("ic_names", rhs.d_init_conditions, TWO_TAB, true) << '\n';

                for (std::map<std::string, InitializationCondition*>::const_iterator pi = rhs.d_init_conditions.begin();
                        pi != rhs.d_init_conditions.end(); ++pi)
                {
                    os << '\n' << LogMessage::get_prefix (TWO_TAB) << pi->second->get_ic_name() << '\n';
                    os << LogMessage::get_prefix (TWO_TAB) << "{\n";
                    oss.str("");
                    oss << *(pi->second);
                    os << LogMessage::get_log_info (oss, THREE_TAB) << '\n';
                    os << LogMessage::get_prefix (TWO_TAB) << "}\n";
                }

                os << LogMessage::get_prefix (ONE_TAB) << "}\n\n";
            }

//        }
    }
    /*
     * output Range  --> domain
     */
    {
        oss.str("");
        oss << *rhs.d_equation_domain << '\n';
        os << LogMessage::get_log_info (oss, ONE_TAB) << '\n';
    }
    /*
     * output system information
     */
    {
        oss.str("");
        oss << '\n' << *(rhs.d_system) << '\n';
//        oss << "system" << "\n{\n";
//        oss << LogMessage::get_prefix (ONE_TAB) << "type = \"IMPLICIT\"" << "\n\n";
//        oss << LogMessage::get_prefix (ONE_TAB) << "solver"<< "\n";
//        oss << LogMessage::get_prefix (ONE_TAB) << "{"<< "\n";
//        oss << LogMessage::get_prefix (TWO_TAB) << "method = \"LINEAR\"" << "\n";
//        oss << LogMessage::get_prefix (ONE_TAB) << "}"<< "\n";
//        oss << "}" << "\n";

        os << LogMessage::get_log_info (oss, ONE_TAB) << '\n';
    }
    /*
     * output irregular_bdry_conds  ---> irregular_boundary_conditions
     */
    {
        oss.str("");
        if (rhs.d_irregular_bdry_conds->is_used())
        {
            oss << *(rhs.d_irregular_bdry_conds);
            os << LogMessage::get_log_info (oss, ONE_TAB) << '\n';
        }
    }

    os << "}";

    return os;
}

} // namespace FreeMAPs
